package cur.nature.gateway.common.config;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

@Slf4j
@Component
@ConditionalOnProperty( value = {"spring.cloud.nacos.discovery.registerEnabled"},matchIfMissing = true)
//https://blog.csdn.net/YinLiaoEr/article/details/140777124
public class DynamicRouteLoader {
    @Autowired
    private RouteDefinitionRepository routeDefinitionRepository;
    @Autowired
    private  NacosConfigManager nacosConfigManager;  //获取与nacos的连接
    @Autowired
    private ApplicationEventPublisher publisher;
    @Autowired
    private RouteLocator routeLocator;

    // 路由配置文件的id和分组
    private final String dataId = "gateway-routes.json";
    private final String group = "DEFAULT_GROUP";
    // 保存更新过的路由id
    private final Set<String> routeIds = new HashSet<>();

    @PostConstruct //bean被实例化之后，Spring容器会立即调用带有@PostConstruct 注解的方法
    public void initRouteConfigListener() throws NacosException {
        // 1.注册监听器并首次拉取配置
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        //更新配置
                        log.info("gateway 路由更新...");
                        updateConfigInfo(configInfo);
                    }
                });

        // 2.首次启动时，更新一次配置
        updateConfigInfo(configInfo);
    }

    private void updateConfigInfo(String configInfo) {
        log.info("监听到路由配置变更，{}", configInfo);
        // 1.解析配置信息,转为RouteDefinition
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 2.更新前先清空旧路由
        // 2.1.清除旧路由
        List<Route> listMono1 = routeLocator.getRoutes().collectList().block();
        listMono1.forEach(s->{
            log.info("路由id1:" + s.getId()+","+s.getPredicate().toString());
        });
        for (String routeId : routeIds) {
//            writer.delete(Mono.just(routeId)).subscribe();
            routeDefinitionRepository.delete(Mono.just(routeId)).subscribe();

        }
        routeIds.clear();

        List<Route> listMono2 = routeLocator.getRoutes().collectList().block();
        listMono2.forEach(s->{
            log.info("路由id2:" + s.getId()+","+s.getPredicate().toString());
        });
        // 2.2.判断是否有新的路由要更新
        if (CollectionUtil.isEmpty(routeDefinitions)) {
            // 无新路由配置，直接结束
            return;
        }
        // 3.更新路由
        routeDefinitions.forEach(routeDefinition -> {
            // 3.1.更新路由
//            writer.save(Mono.just(routeDefinition)).subscribe();
            routeDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
            // 3.2.记录路由id，方便将来删除
            routeIds.add(routeDefinition.getId());
        });
        this.publisher.publishEvent(new RefreshRoutesEvent(this));

        List<Route> listMono = routeLocator.getRoutes().collectList().block();
        listMono.forEach(s->{
            log.info("路由id3:" + s.getId()+","+s.getPredicate().toString());
        });
    }
}
